using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;

using Pegasus.Diagnostics;
using Pegasus.Log4Net;
using Pegasus.Runtime.Serialization.Formatters.Xml;

namespace Pegasus.Collections.Generic
{
	/// <summary>
	/// This diectionary know how to persist itself and save value in the windows current user data directory.
	/// </summary>
	/// <typeparam name="TKey"></typeparam>
	/// <typeparam name="TValue"></typeparam>
	[Serializable]
	public class DictionaryApplicationData<TKey, TValue> : Dictionary<TKey, TValue>
	{
		// Local Instance Values
		[NonSerialized]
		private ILog m_log = LogManager.GetLogger( typeof( DictionaryApplicationData<TKey, TValue> ) );

		[NonSerialized]
		private string m_storeName = string.Empty;

		[NonSerialized]
		private string m_filename = string.Empty;

		/// <summary>
		/// Initializes a new instance of the <see cref="DictionaryApplicationData&lt;TKey, TValue&gt;"/> class.
		/// </summary>
		/// <param name="storeName">Name of the store.</param>
		public DictionaryApplicationData( string storeName )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( storeName, "storeName" );
			InitializeDictionary( storeName );
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DictionaryApplicationData&lt;TKey, TValue&gt;"/> class.
		/// </summary>
		/// <param name="storeName">Name of the store.</param>
		/// <param name="dictionary">The dictionary.</param>
		public DictionaryApplicationData( string storeName, IDictionary<TKey, TValue> dictionary )
			: base( dictionary )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( storeName, "storeName" );
			ParamCode.AssertNotNull( dictionary, "dictionary" );

			InitializeDictionary( storeName );
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DictionaryApplicationData&lt;TKey, TValue&gt;"/> class.
		/// </summary>
		/// <param name="storeName">Name of the store.</param>
		/// <param name="comparer">The comparer.</param>
		public DictionaryApplicationData( string storeName, IEqualityComparer<TKey> comparer )
			: base( comparer )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( storeName, "storeName" );
			ParamCode.AssertNotNull( comparer, "comparer" );

			InitializeDictionary( storeName );
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DictionaryApplicationData&lt;TKey, TValue&gt;"/> class.
		/// </summary>
		/// <param name="storeName">Name of the store.</param>
		/// <param name="capacity">The capacity.</param>
		public DictionaryApplicationData( string storeName, int capacity )
			: base( capacity )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( storeName, "storeName" );
			ParamCode.AssertRange( capacity, 0, int.MaxValue, "capacity" );

			InitializeDictionary( storeName );
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DictionaryApplicationData&lt;TKey, TValue&gt;"/> class.
		/// </summary>
		/// <param name="storeName">Name of the store.</param>
		/// <param name="dictionary">The dictionary.</param>
		/// <param name="comparer">The comparer.</param>
		public DictionaryApplicationData( string storeName, IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer )
			: base( dictionary, comparer )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( storeName, "storeName" );
			ParamCode.AssertNotNull( dictionary, "dictionary" );
			ParamCode.AssertNotNull( comparer, "comparer" );

			InitializeDictionary( storeName );
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DictionaryApplicationData&lt;TKey, TValue&gt;"/> class.
		/// </summary>
		/// <param name="storeName">Name of the store.</param>
		/// <param name="capacity">The capacity.</param>
		/// <param name="comparer">The comparer.</param>
		public DictionaryApplicationData( string storeName, int capacity, IEqualityComparer<TKey> comparer )
			: base( capacity, comparer )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( storeName, "storeName" );
			ParamCode.AssertRange( capacity, 0, int.MaxValue, "capacity" );
			ParamCode.AssertNotNull( comparer, "comparer" );

			InitializeDictionary( storeName );
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DictionaryApplicationData&lt;TKey, TValue&gt;"/> class.
		/// </summary>
		/// <param name="info">A <see cref="T:System.Runtime.Serialization.SerializationInfo"></see> object containing the information required to serialize the <see cref="T:System.Collections.Generic.Dictionary`2"></see>.</param>
		/// <param name="context">A <see cref="T:System.Runtime.Serialization.StreamingContext"></see> structure containing the source and destination of the serialized stream associated with the <see cref="T:System.Collections.Generic.Dictionary`2"></see>.</param>
		protected DictionaryApplicationData( SerializationInfo info, StreamingContext context )
			: base( info, context )
		{
			// Check Parameters
			ParamCode.AssertNotNull( info, "info" );
			ParamCode.AssertNotNull( context, "context" );
		}

		/// <summary>
		/// Gets the name of the store.
		/// </summary>
		/// <value>The name of the store.</value>
		public string StoreName
		{
			get
			{
				return m_storeName;
			}
		}

		/// <summary>
		/// Gets the filename for the underlying xml file.
		/// </summary>
		/// <value>The filename.</value>
		public string Filename
		{
			get
			{
				return m_filename;
			}
		}

		/// <summary>
		/// Saves this instance.
		/// </summary>
		public void Save()
		{
			SaveDictionary( this );
		}

		/// <summary>
		/// Initializes the store.
		/// </summary>
		/// <param name="storeName">Name of the store.</param>
		private void InitializeDictionary( string storeName )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( storeName, "storeName" );

			// Set the store name for this dictionary.
			m_storeName = storeName;
			m_filename = GetStoreFilename();

			DictionaryApplicationData<TKey, TValue> dictionary = OpenDictionary();
			if( dictionary != null )
			{
				foreach( KeyValuePair<TKey, TValue> item in dictionary )
				{
					this.Add( item.Key, item.Value );
				}
			}
		}

		/// <summary>
		/// Gets the store filename.
		/// </summary>
		/// <returns></returns>
		protected virtual string GetStoreFilename()
		{
			string folder = Environment.GetFolderPath( Environment.SpecialFolder.CommonApplicationData );
			string filename = StoreName + ".xml";

			return Path.Combine( folder, filename );
		}

		/// <summary>
		/// Gets the saved dictionary.
		/// </summary>
		/// <returns></returns>
		private DictionaryApplicationData<TKey, TValue> OpenDictionary()
		{
			try
			{
				using( FileStream stream = File.Open( m_filename, FileMode.Open, FileAccess.Read, FileShare.Read ) )
				{
					XmlFormatter2 formatter = new XmlFormatter2();
					return (DictionaryApplicationData<TKey, TValue>) formatter.Deserialize( stream );
				}
			}
			catch( Exception e )
			{
				m_log.Error( "Error opening dictionary file.  Ignoring this and just initializing the dictionary", e );
			}

			return null;
		}

		/// <summary>
		/// Gets the saved dictionary.
		/// </summary>
		/// <returns></returns>
		private void SaveDictionary( DictionaryApplicationData<TKey, TValue> dictionary )
		{
			try
			{
				using( FileStream stream = File.Open( m_filename, FileMode.Create, FileAccess.Write, FileShare.None ) )
				{
					XmlFormatter2 formatter = new XmlFormatter2();
					formatter.Serialize( stream, dictionary );
				}
			}
			catch( Exception e )
			{
				m_log.Error( "Error saving dictionary file.  Ignoring this and just initializing the dictionary", e );
			}
		}
	}
}
